/**
 * 
 */
package org.dse.analysis.dict;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashSet;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * This class is the dictionary. It contains all words, storing them in a tree
 * structure.
 * 
 * The dictionary just holds the root of the tree. It loads words from the files
 * and stores them from the root. It also provides search function, searching a
 * word from the root. At last, it can give the character type, like letter,
 * number, CJK.
 * 
 * @author zhangchen
 * 
 */
public class Dictionary {

	public static final int BASECHARTYPE_NULL = 0x00000000;

	public static final int BASECHARTYPE_LETTER = 0x00010000;

	public static final int BASECHARTYPE_NUMBER = 0x00100000;

	public static final int NUMBER_CHN = 0x00000001;

	public static final int NUMBER_OTHER = 0x00000002;

	public static final int BASECHARTYPE_CJK = 0x01000000;

	public static final int BASECHARTYPE_OTHER = 0x10000000;

	public static final int OTHER_CONNECTOR = 0x00000004;

	public static final int OTHER_NUMSIGN = 0x00000008;

	private static Dictionary singleton = null;

	private static final String file_Word = "/dict/word/wordbase.dic";

	private static final String file_stop_Word = "/dict/stopword/stopword.dic";

	private static final String file_suffix_Word = "/dict/suffix/suffix.dic";

	private static final String file_local_Word = "/dict/local/local.dic";

	private static final String file_OtherDigit = "/dict/other_digit.dic";

	private static final String file_CHNNumber = "/dict/c_number.dic";

	private static final String file_NbSign = "/dict/number_sign.dic";

	private static final String file_Connector = "/dict/connector.dic";

	private static final String file_Count = "/dict/count.dic";

	private static final String file_noise_char = "/dict/noisechar.dic";

	private Set<String> hsNoise = null;

	private Set<String> hsStop = null;

	private Set<String> hsOtherDigit = null;

	private Set<String> hsCHNNumber = null;

	private Set<String> hsNbSign = null;

	private Set<String> hsConnector = null;

	private DictSegment dictSeg;

	private static final Log log = LogFactory.getLog(Dictionary.class);

	private Dictionary() {
		dictSeg = new DictSegment();
		initDictionary();
	}

	/**
	 * Get the dictionary singleton.
	 * 
	 * @return the singleton
	 */
	public static Dictionary load() {
		if (singleton == null) {
			singleton = new Dictionary();
		}
		return singleton;
	}

	// load word data from files
	private void initDictionary() {
		loadNoiseWords();
		loadStopWords();

		loadOtherDigit();
		loadCHNNumber();
		loadNbSign();
		loadConnector();

		loadSuffixWords();
		loadCountWords();
		loadWords();
		loadLocalWords();
	}

	private void loadData(Set<String> set, InputStream is) {
		String theWord = "";
		try {
			BufferedReader br = new BufferedReader(new InputStreamReader(is,
					"GBK"), 512);
			do {
				theWord = br.readLine();
				if (theWord != null) {
					set.add(theWord.trim());
				}
			} while (theWord != null);
		} catch (IOException ioe) {
			System.err.println("Exception in loading dictionary data!");
			ioe.printStackTrace();
			log.error("Exception in loading dictionary data!", ioe);
		} finally {
			try {
				if (is != null) {
					is.close();
					is = null;
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	private void loadData(WordType type, InputStream is) {
		String theWord = "";
		try {
			BufferedReader br = new BufferedReader(new InputStreamReader(is,
					"GBK"), 512);
			do {
				theWord = br.readLine();
				if (theWord != null) {
					theWord = theWord.trim();
					this.dictSeg.addWord(theWord.toCharArray(), type);
				}
			} while (theWord != null);
		} catch (IOException ioe) {
			System.err.println("Exception in loading word library!");
			ioe.printStackTrace();
			log.error("Exception in loading word library!", ioe);
		} finally {
			try {
				if (is != null) {
					is.close();
					is = null;
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * Judge the char is a connector.
	 * 
	 * @param onechar
	 *            the char to be judged
	 * @return a boolean value to show whether it is a connector
	 */
	public boolean isConnector(char onechar) {
		return singleton.hsConnector.contains(String.valueOf(onechar));
	}

	private void loadConnector() {
		InputStream is = Dictionary.class
				.getResourceAsStream(Dictionary.file_Connector);
		this.hsConnector = new HashSet<String>(4);
		loadData(this.hsConnector, is);
	}

	/**
	 * Judge the char is a number sign.
	 * 
	 * @param onechar
	 *            the char to be judged
	 * @return a boolean value to show whether it is a number sign
	 */
	public boolean isNbSign(char onechar) {
		return singleton.hsNbSign.contains(String.valueOf(onechar));
	}

	private void loadNbSign() {
		InputStream is = Dictionary.class
				.getResourceAsStream(Dictionary.file_NbSign);
		this.hsNbSign = new HashSet<String>(4);
		loadData(this.hsNbSign, is);
	}

	/**
	 * Judge the char is a number.
	 * 
	 * @param onechar
	 *            the char to be judged
	 * @return a boolean value to show whether it is a number
	 */
	public boolean isNumber(char onechar) {
		return Character.isDigit(onechar) || isOtherDigit(onechar)
				|| isCHNNumber(onechar);
	}

	/**
	 * Judge the char is a Chinese number.
	 * 
	 * @param onechar
	 *            the char to be judged
	 * @return a boolean value to show whether it is a Chinese number
	 */
	public boolean isCHNNumber(char onechar) {
		return singleton.hsCHNNumber.contains(String.valueOf(onechar));
	}

	private void loadCHNNumber() {
		InputStream is = Dictionary.class
				.getResourceAsStream(Dictionary.file_CHNNumber);
		this.hsCHNNumber = new HashSet<String>(32);
		loadData(this.hsCHNNumber, is);
	}

	/**
	 * Judge the char is a number, but not normal number and Chinese number.
	 * 
	 * @param onechar
	 *            the char to be judged
	 * @return a boolean value to show whether it is an other digit
	 */
	public boolean isOtherDigit(char onechar) {
		return singleton.hsOtherDigit.contains(String.valueOf(onechar));
	}

	private void loadOtherDigit() {
		InputStream is = Dictionary.class
				.getResourceAsStream(Dictionary.file_OtherDigit);
		this.hsOtherDigit = new HashSet<String>(32);
		loadData(this.hsOtherDigit, is);
	}

	/**
	 * Judge the char is a useless word.
	 * 
	 * @param onechar
	 *            the char to be judged
	 * @return a boolean value to show whether it is a useless word
	 */
	public boolean isUselessWord(String word) {
		return isStopWord(word) || isNoiseWord(word);
	}

	/**
	 * Judge the char is a stop word.
	 * 
	 * @param onechar
	 *            the char to be judged
	 * @return a boolean value to show whether it is a stop word
	 */
	public boolean isStopWord(String word) {
		return singleton.hsStop.contains(word);
	}

	private void loadStopWords() {
		InputStream is = Dictionary.class
				.getResourceAsStream(Dictionary.file_stop_Word);
		this.hsStop = new HashSet<String>(64);
		loadData(this.hsStop, is);
	}

	/**
	 * Judge the char is a noise word.
	 * 
	 * @param onechar
	 *            the char to be judged
	 * @return a boolean value to show whether it is a noise word
	 */
	public boolean isNoiseWord(String word) {
		return singleton.hsNoise.contains(word);
	}

	private void loadNoiseWords() {
		InputStream is = Dictionary.class
				.getResourceAsStream(Dictionary.file_noise_char);
		this.hsNoise = new HashSet<String>(64);
		loadData(this.hsNoise, is);
	}

	private void loadCountWords() {
		InputStream is = Dictionary.class
				.getResourceAsStream(Dictionary.file_Count);
		loadData(WordType.WT_COUNT, is);
	}

	private void loadSuffixWords() {
		InputStream is = Dictionary.class
				.getResourceAsStream(Dictionary.file_suffix_Word);
		loadData(WordType.WT_SUFFIX, is);
	}

	private void loadWords() {
		InputStream is = Dictionary.class
				.getResourceAsStream(Dictionary.file_Word);
		loadData(WordType.WT_NORMWORD, is);
	}

	private void loadLocalWords() {
		InputStream is = Dictionary.class
				.getResourceAsStream(Dictionary.file_local_Word);
		loadData(WordType.WT_NORMWORD, is);
	}

	/**
	 * Search a word in the dictionary.
	 * 
	 * @param seg
	 *            the char array to be searched
	 * @return the search result hit
	 */
	public Hit search(char[] seg) {
		return singleton.dictSeg.search(seg, 0, seg.length - 1);
	}

	/**
	 * Search a word in the dictionary, overloading.
	 * 
	 * @param seg
	 *            the char array
	 * @param beginIndex
	 *            the begin index of the char array to be searched
	 * @param endIndex
	 *            the end index
	 */
	public Hit search(char[] seg, int beginIndex, int endIndex) {
		return singleton.dictSeg.search(seg, beginIndex, endIndex);
	}

	/**
	 * Give the type of a char.
	 * 
	 * @param onechar
	 *            the char
	 * @return an integer representing the type
	 */
	public int identify(char onechar) {
		int charType = Dictionary.BASECHARTYPE_NULL;

		if ((onechar >= 'a' && onechar <= 'z')
				|| (onechar >= 'A' && onechar <= 'Z')) {
			charType = charType | Dictionary.BASECHARTYPE_LETTER;
		}

		if (this.isNumber(onechar)) {
			charType = charType | Dictionary.BASECHARTYPE_NUMBER;
			if (this.isCHNNumber(onechar)) {
				charType = charType | Dictionary.NUMBER_CHN;
			}
			if (this.isOtherDigit(onechar)) {
				charType = charType | Dictionary.NUMBER_OTHER;
			}
		}

		if (Character.UnicodeBlock.of(onechar) == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS) {
			charType = charType | Dictionary.BASECHARTYPE_CJK;
		}

		if (charType == Dictionary.BASECHARTYPE_NULL) {
			charType = charType | Dictionary.BASECHARTYPE_OTHER;
			if (this.isNbSign(onechar)) {
				charType = charType | Dictionary.OTHER_NUMSIGN;
			}
			if (this.isConnector(onechar)) {
				charType = charType | Dictionary.OTHER_CONNECTOR;
			}
		}
		return charType;
	}

	/*
	 * public static void main(String[] args) { Dictionary dict =
	 * Dictionary.load(); Hit hit = dict.search("".toCharArray());
	 * System.out.println("Match : " + hit.isMatch());
	 * System.out.println("PrefixMatch : " + hit.isPrefixMatch());
	 * System.out.println("MatchAndContinue : " + hit.isMatchAndContinue());
	 * System.out.println("Unmatch : " + hit.isUnmatch());
	 * System.out.println("-----------------"); if (hit.isMatch()) {
	 * System.out.println("NormWord : " + hit.getWordType().isNormWord());
	 * System.out.println("Suffix : " + hit.getWordType().isSuffix());
	 * System.out.println("Count : " + hit.getWordType().isCount()); } try {
	 * Thread.sleep(15000); } catch (InterruptedException e) {
	 * e.printStackTrace(); } }
	 */

}
